package othello.client.playroom;

import gwt.canvas.client.Canvas;
import othello.client.service.OthelloServiceAsync;
import othello.client.utility.screen.Screen;
import othello.client.utility.screen.View;
import othello.shared.model.GameStep;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;

/**
 * オセロの入力インターフェース実装クラス。
 * 
 * @author onjiro.mohyahya
 */
class ControllerForGameBoard {

	/**
	 * クリック処理を通知するハンドラー。現在のフォーカス位置に石を配置します。
	 */
	private final ClickHandler clickHandler = new ClickHandler() {
		@Override
		public void onClick(ClickEvent event) {
			setTo(focusPosition);
		}
	};

	/**
	 * マウスの移動処理を通知するハンドラー。マウス位置をフォーカスします。
	 */
	private final MouseMoveHandler mouseMoveHandler = new MouseMoveHandler() {
		@Override
		public void onMouseMove(MouseMoveEvent event) {
			focusTo(convertor.toGameBoardPosition(event.getX(), event.getY()));
		}
	};

	private final GameBoard model;

	private final OthelloServiceAsync service;

	/**
	 * 当コントローラーのビュー
	 */
	private final ViewForController view;

	/**
	 * ゲーム盤の位置と画面上の位置の変換器
	 */
	private final ImageSettings.PositionConvertor convertor;

	/**
	 * ゲーム盤で現在指定されている位置
	 */
	private Position focusPosition = new Position(0, 0);

	/**
	 * 入力を受け付けるCanvasを引数に取るコンストラクター
	 * 
	 * @param canvas
	 *            入力を受け付けるCanvas要素
	 */
	public ControllerForGameBoard(GameBoard model, OthelloServiceAsync service,
			Canvas canvas, ImageSettings images) {
		canvas.addClickHandler(this.clickHandler);
		canvas.addMouseMoveHandler(this.mouseMoveHandler);
		this.model = model;
		this.view = new ViewForController(images);
		this.convertor = images.getPositionConvertor();
		this.service = service;
	}

	/**
	 * <p>
	 * 指定箇所に石を配置します。
	 * </p>
	 * <p>
	 * 石の配置が可能な場合、次の処理が行われます。
	 * <ul>
	 * <li>ゲーム盤への石の配置の適用</li>
	 * <li>相手側への石の配置情報の送信</li>
	 * </ul>
	 * </p>
	 * 
	 * @param x
	 *            石を配置するゲーム盤の座標x
	 * @param y
	 *            石を配置するゲーム盤の座標y
	 */
	private void setTo(Position position) {
		// 相手ターンに対するガード句
		if (!this.model.isMyTurn()) {
			Window.alert("This is not your turn. Please wait a minute.");
			return;
		}

		// 指定箇所に配置可能か確認
		GameStep newStep = this.model.calculateForPutOn(position.x, position.y);
		if (newStep == null) {
			return;
		}

		// 配置可能な場合、配置を適用、相手側に配置情報を通知
		this.model.apply(newStep);
		this.service.entryNextStep(this.model.getId(), newStep,
				new AsyncCallback<Void>() {
					@Override
					public void onSuccess(Void result) {
					}

					@Override
					public void onFailure(Throwable caught) {
						Window.alert("step apply failure.");
					}
				});
	}

	/**
	 * <p>
	 * 指定箇所にフォーカスを移動します。
	 * </p>
	 * 
	 * @param x
	 *            フォーカス対象のゲーム盤座標x
	 * @param y
	 *            フォーカス対象のゲーム盤座標y
	 */
	private void focusTo(Position newPosition) {
		if (!this.focusPosition.equals(newPosition)) {
			Position lastPosition = this.focusPosition;
			this.focusPosition = newPosition;
			this.view.update(lastPosition);
		}
	}

	/**
	 * 当コントローラーのビューを返します。
	 * 
	 * @return
	 */
	public View getView() {
		return this.view;
	}

	/**
	 * ユーザーからの入力情報を表示するビュー
	 * 
	 * @author onjiro.mohyahya
	 */
	private final class ViewForController extends
			othello.client.utility.screen.View {

		private final Position cellSize;

		public ViewForController(ImageSettings images) {
			this.cellSize = images.getCellSize();
		}

		/**
		 * 表示対象のモデルが変更された通知を受け取ります。
		 * 
		 * @param lastX
		 *            モデルの前回のX座標
		 * @param lastY
		 *            モデルの前回のY座標
		 */
		public final void update(Position lastPosition) {
			super.notifyUpdate();
		}

		/**
		 * 以下の情報を描画します。
		 * <ul>
		 * <li>現在選択されているマス</li>
		 * </ul>
		 */
		@Override
		public void draw(Screen screen) {
			if (focusPosition.x < 0 || focusPosition.x > 7
					|| focusPosition.y < 0 || focusPosition.y > 7) {
				return;
			}

			Position position = convertor.toPixelPosition(focusPosition.x,
					focusPosition.y);
			screen.getCanvas().setFillStyle("rgba(0, 0, 255, 0.4)");
			screen.getCanvas().fillRect(position.x, position.y,
					this.cellSize.x, this.cellSize.y);
		}
	}

}